package com.abel.bigwater.data.sanchuan

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.data.Bit32Array
import com.abel.bigwater.data.NotEnoughDataException
import com.abel.bigwater.model.JsonDateSerializer
import com.abel.bigwater.model.JsonHelper
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.annotation.JSONField
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import io.netty.buffer.ByteBuf
import org.joda.time.DateTime
import org.joda.time.LocalTime
import org.slf4j.LoggerFactory
import java.util.*
import kotlin.experimental.and

class MeterOnlineData(_dataLen: Int?) : SanchuanData(_dataLen) {

    /**
     * 4 字节，年年月日（采用 BCD 码）。详见参数表，参数编码 0006。
    3 字节，时分秒（采用 BCD 码）。详见参数表，参数编码 0006。
    注意，水表上传数据一般为次日上传当日数据，若此处日期为 1 月 2 日，时间为 3 时 4 分 5 秒，
    那么对本帧数据来讲：
    1、间隔流量数据，是发生在 1 月 2 日 0 点 0 分 0 秒至 1 月 3 日 0 点 0 分 0 秒之间。
    2、日结累计流量 M，是在 1 月 3 日 0 点 0 分 0 秒纪录下来的。日结累计流量 M-1，是在 1 月 2
    日 0 点 0 分 0 秒纪录下来的，依此类推
    3、软件版本、电池电压、累计发送次数、当前累计流量、状态字、内部温度均是在 1 月 3 日 3
    时 4 分 5 秒纪录下来的。
    4、 RSRP、 SNR、 PCI、信号覆盖等级、 IMSI。若为掉电模式，则是在 1 月 3 日 3 时 4 分 5 秒纪
    录下来的. 若为 PSM 模式，则是在 1 月 3 日 3 时 4 分 5 秒的前一次上线时间纪录下来的。
    当前默认按掉电模式处理
    5、密集采样数据，是从密集采样起始时间开始的。
     */
    var sampleTimeStr: BcdString? = null

    @JsonSerialize(using = JsonDateSerializer::class)
    @JSONField(format = JsonHelper.FULL_DATE_FORMAT)
    var sampleTime: Date? = null
        set(value) {
            field = value

            sampleTimeStr =
                    if (value == null) null
                    else BcdString.fromSanchuanDate(value)
        }
        get() {
            field = sampleTimeStr?.toSanchunDate()
            return field
        }

    /**
     * 上线帧指示 BIT0 1 上线类型为 1 天 0 其他制定： 审核： 批准：
    第 5 页
    BIT1-3 无线模组型号 0 移远模组 BC95HB
    1 利尔达模组 LSD4NBN
    2 M5310 模组
    3 移远模组 BC95HA
    4-6 预留
    7 未识别
    BIT4 PCI 和内部温度上报使能 1 上报 0 不上报
    BIT5-7 预留
     */
    var frameFlag: FrameFlagType? = null

    /**
     * 电池电压	16进制低字节在前，单位0.01V，例：0x00,0x01表示2.56V
    指示发送数据时刻的电池电压值
     */
    var volt: Float? = null

    /**
     * 累计上线成功次数	16进制低字节在前。表示累计上线成功的次数。
     */
    var totalOnline: Int? = null

    /**
     * 累计上线失败次数	16进制低字节在前。表示累计上线失败的次数。
     */
    var totalFail: Int? = null

    /**
     * ref to 3.2 GM状态ST:STA
     */
    var gmState: GmStateType? = null

    /**
     * rsrp， 2 字节有符号，低字节在前
    -999 表示还没有读取过 RSSI， -998 表示读取 RSSI 出错， -997 表示模组断开信号
     */
    var rsrp: Short? = null

    /**
     * 信噪比 SNR， 2 字节有符号，低字节在前，有效值的范围为-300~300。单位 0.1。
    -999 表示还没有读取过 SNR， -998 表示读取 SNR 出错， -997 表示模组断开信号
     */
    var snr: Short? = null

    /**
     * 信号覆盖等级 有效值的范围为 0-2
    9 表示还没有读取过信号覆盖等级， 8 表示读取信号覆盖等级出错， 7 表示模组断开信号
     */
    var signalOverlapGrade: Short? = null

    /**
     * PCI 16 进制无符号，低字节在前（上线帧指示 BIT4 为 1 时上报，否则不上报）
     */
    var pci: Int? = null

    /**
     * 内部温度 16 进制有符号数，低字节在前，单位 0.01 摄氏度（上线帧指示 BIT4 为 1 时上报，否则不上报）
    0x7FFF（按低字节在前即为 FF 7F）表示没有内部温度值。
     */
    var temperatureDigits: Short? = null

    @JSONField(serialize = false)
    var temperature: Float? = null
        get() {
            field =
                    if (temperatureDigits == null || temperatureDigits!!.toInt().and(0x7FFF) == 0x7FFF) null
                    else (0.01.toFloat() * temperatureDigits!!.toFloat())

            return field
        }
        set(value) {
            field = value
            temperatureDigits = if (value == null) 0x7FFF else (100 * value).toShort()
        }

    /**
     * 软件版本号 格式详见参数表
     */
    var softVersion: SoftVersionType? = null

    /**
     * 8 IMSI 号 格式详见参数表
     * IMEI 03 0C 8
    0x01,0x23,0x45,0x67,0x89,0x01,0x23,0x45 表示 123456789012345
    写时为将 IMEI 写入到模组中，写完后续发指令确认是否写入成功（已写 IMEI 的
    模组不可再次写入）
    上线过程中不可写
     */
    var imsi: BcdString? = null

    /**
     * 5 当前累计流量 格式见参数表，参数编码 1110
     * 累计流量
    格式 2 11 10 5
    16 进制无符号低字节在前，单位 0.1L。
    例： 0x00,0x01,0x00,0x00,0x00 表示当前累计流量为： 25.6L
    表示水表从激活后到当前时间累计流过的总水量。注意与参数编码(0101)格式不
    一致。
     */
    var totalReadingStr: BcdString? = null

    /**
     * unit: m³
     */
    var totalReading: Double? = null
        get() {
            field = totalReadingStr?.toUlongLEHex()!! * 0.1 * 0.001
            return field
        }
        set(value) {
            field = value
            totalReadingStr = BcdString.fromUlongLEHex((value!! * 10 * 1000).toLong())
        }

    /**
     * 5 当前累计逆流量 格式见参数表，参数编码 1110
     */
    var totalReverseReadingStr: BcdString? = null

    /**
     * unit: m³
     */
    var totalReservseReading: Double? = null
        get() {
            field = totalReverseReadingStr?.toUlongLEHex()!! * 0.1 * 0.001
            return field
        }
        set(value) {
            field = value
            totalReverseReadingStr = BcdString.fromUlongLEHex((value!! * 10 * 1000).toLong())
        }

    var dayBillData: DayBillDataPart? = null

    var periodReadData: PeriodReadDataPart? = null

    var intensiveData: IntensiveDataPart? = null

    var warn: WarnDataPart? = null

    /**
     * 7
    瞬时流速最大值 4 字节有符号，单位 L/h。 详见参数表，参数编码 020D
    对应时间 3 字节，时分秒（采用 BCD 码）。详见参数表，参数编码 0006
    瞬时流量，单位 L/h。 4 字节， 32 位有符号整数， 16 进制低字节在前。正表示正
    流，负表示逆流。写无效。
    0x34 0x12 0x00 0x00，表示瞬时流量为正流 4660L/h
     */
    var maxFlowInt: Int? = null

    /**
     * unit: m³
     * computing field for {@link maxFlowInt}.
     */
    var maxFlow: Double? = null
        get() {
            field = if (maxFlowInt == null) null else maxFlowInt!! / 1000.0
            return field
        }
        set(value) {
            field = value
            maxFlowInt = if (field == null) null else (1000 * field!!).toInt()
        }

    /**
    对应时间 3 字节，时分秒（采用 BCD 码）。详见参数表，参数编码 0006
     * 表内时间 00 06 7 YYYYMMDDHHmmSS 表内时间（采用 BCD 码）。
    例： 0x20,0x14,0x01,0x02,0x03,0x04,0x05，表示 2014 年 1 月 2 日 3 时 4 分 5 秒
     */
    var maxFlowTimeStr: BcdString? = null

    /**
     * computing field for {@link maxFlowTimeStr}.
     */
    @JSONField(serialize = false)
    @JsonIgnore
    var maxFlowLocalTime: LocalTime? = null
        get() {
            field = if (maxFlowTimeStr == null) null else LocalTime(
                    maxFlowTimeStr!!.bcdArray!![0].toInt(),
                    maxFlowTimeStr!!.bcdArray!![1].toInt(),
                    maxFlowTimeStr!!.bcdArray!![2].toInt()
            )

            return field
        }
        set(value) {
            field = value
            maxFlowTimeStr = if (field == null) null else
                BcdString(byteArrayOf(field!!.hourOfDay.toByte(),
                        field!!.minuteOfHour.toByte(),
                        field!!.secondOfMinute.toByte()), true)
        }

    @JsonSerialize(using = JsonDateSerializer::class)
    @JSONField(format = JsonHelper.FULL_DATE_FORMAT)
    var maxFlowTime: Date? = null
        get () {
            return DateTime.now().withTimeAtStartOfDay()
                    .withHourOfDay(maxFlowLocalTime!!.hourOfDay)
                    .withMinuteOfHour(maxFlowLocalTime!!.minuteOfHour)
                    .withSecondOfMinute(maxFlowLocalTime!!.secondOfMinute)
                    .toDate()
        }


    /**
     * 计算数据长度，针对解析和打包
     */
    override fun computeDataLen(): Int {
        // 时间 + 上线帧指示 + 电池电压 + 累计上线成功次数 + 累计上线失败次数
        // 状态字 + RSRP + 信噪比 + 信号覆盖等级 + PCI + 内部温度 + 软件版本号 + IMSI 号
        // 当前累计流量 + 当前累计逆流量
        // 瞬时流速最大值
        //对应时间
        dataLen = 7 + 1 + 2 + 2 + 2 + 4 + 2 + 2 + 1 + 2 + 2 + 3 + 8 + 5 + 5 + dayBillData!!.computeDataLen() + periodReadData!!.computeDataLen() + intensiveData!!.computeDataLen() + warn!!.computeDataLen() + 7

        return dataLen!!
    }

    /**
     * 从当前位置写入打包字节流
     */
    override fun fillBuffer(buf: ByteBuf) {
        val tmp = buf.alloc().buffer().apply {
            computeDataLen()

            assert(sampleTimeStr!!.size == 7)
            writeBytes(sampleTimeStr!!.hexArray)

            writeByte(frameFlag!!.back)

            writeShortLE((volt!! * 100).toInt().and(0xFFFF))

            // 2 累计上线成功次数 16 进制低字节在前。表示累计上线成功的次数。
            // 2 累计上线失败次数 16 进制低字节在前。表示累计上线失败的次数。
            writeShortLE(totalOnline!!)

            writeShortLE(totalFail!!)

            // 4 状态字 见 ST 状态字
            writeIntLE(gmState!!.back)

            writeShortLE(rsrp!!.toInt())

            writeShortLE(snr!!.toInt())

            writeByte(signalOverlapGrade!!.toInt())

            writeShortLE(pci!!)

            writeShortLE(temperatureDigits!!.toInt())

            // 3 软件版本号 格式详见参数表
            writeShortLE(softVersion!!.mainVersion!!)
            writeByte(softVersion!!.childVersion!!.toInt())

            assert(imsi!!.size == 8)
            writeBytes(imsi!!.hexArray)

            // 5 当前累计流量 格式见参数表，参数编码 1110
            writeBytes(totalReadingStr!!.hexArray)

            // 5 当前累计逆流量 格式见参数表，参数编码 1110
            writeBytes(totalReverseReadingStr!!.hexArray)

            // 日结数据
            dayBillData!!.fillBuffer(this)

            // 间隔流量
            periodReadData!!.fillBuffer(this)

            // 密集采样数据
            intensiveData!!.fillBuffer(this)

            // 需报警
            warn!!.fillBuffer(this)

            // 瞬时流速最大值
            writeIntLE(maxFlowInt!!.toInt())

            // 对应时间
            assert(maxFlowTimeStr!!.size == 3)
            writeBytes(maxFlowTimeStr!!.hexArray)
        }

        buf.writeBytes(tmp)
    }

    /**
     * 解析字节流，从buf当前位置开始，并且消费其中的字节。
     */
    override fun <T : SanchuanData> parseBuffer(buf: ByteBuf): T? {
        if (buf.readableBytes() < MIN_LEN) throw NotEnoughDataException("数据明显不足: ${buf.readableBytes()}")

        // consume bytes
        buf.readBytes(dataLen!!).apply {
            sampleTimeStr = BcdString.create(this, 7)
            lgr.info("时间: ${DateTime(sampleTime).toString("yyyy-MM-dd'T'HH:mm:ss")}")

            frameFlag = FrameFlagType(readByte().toInt().and(0xFF))

            volt = readShortLE().toInt().and(0xFFFF) * 0.01.toFloat()

            totalOnline = readShortLE().toInt().and(0xFFFF)
            totalFail = readShortLE().toInt().and(0xFFFF)

            gmState = GmStateType(readIntLE())

            rsrp = readShortLE()

            snr = readShortLE()

            signalOverlapGrade = readByte().toShort().and(0xFF)

            pci = readShortLE().toInt().and(0xFFFF)

            temperatureDigits = readShortLE()

            softVersion = SoftVersionType().apply {
                this.mainVersion = readShortLE().toInt().and(0xFFFF)
                this.childVersion = readByte().toShort().and(0xFF)
            }

            imsi = BcdString.create(this, 8)

            totalReadingStr = BcdString.create(this, 5)
            totalReverseReadingStr = BcdString.create(this, 5)
            lgr.info("当前累计流量:${totalReading}, 当前累计逆流量:${totalReservseReading}")

            dayBillData = DayBillDataPart(null).parseBuffer<DayBillDataPart>(this)
            lgr.info("日结数据: ${JSON.toJSONString(dayBillData, true)}")

            periodReadData = PeriodReadDataPart(null).parseBuffer(this)
            lgr.info("间隔流量: ${JSON.toJSONString(periodReadData, true)}")

            intensiveData = IntensiveDataPart(null).parseBuffer<IntensiveDataPart>(this)

            warn = WarnDataPart(null).parseBuffer(this)

            maxFlowInt = readIntLE()

            maxFlowTimeStr = BcdString.create(this, 3)

            if (readableBytes() > 0) {
                lgr.warn("未消费字节数: ${readableBytes()}")
            }
        }

        return this as T
    }

    companion object {
        const val MIN_LEN = 7 + 1 + 2 + 2 + 2 + 4 + 2 + 2 + 1 + 2 + 2 + 3 + 8 + 5 + 5 + 1 + 1 + 1 + 1 + 7

        private val lgr = LoggerFactory.getLogger(MeterOnlineData::class.java)
    }
}

/**
 * 件版本号 00 02 3
主版本 2 字节， 低字节在前， 0x80 0x25 表示主版本为 9600 版本
子版本 1 字节， 0x14 表示子版本为 20 版本
该指令读有效，写无效。
 */
class SoftVersionType {
    var mainVersion: Int? = null

    var childVersion: Short? = null
}

/**
 * 上线帧指示	BIT0  1 上线类型为1天    0 其他
BIT1-3 无线模组型号 0 移远模组BC95HB
1 利尔达模组LSD4NBN
2 M5310模组
3移远模组BC95HA
4-6预留
7 未识别
BIT4  PCI和内部温度上报使能  1 上报  0 不上报
BIT5-7  预留
 */
class FrameFlagType(v1: Int) : Bit32Array(v1) {

    var onlineType: Int
        set(value) {
            set(1, value)
        }
        get() = get(1)

    var wmoduleType: WirelessType?
        set(value) {
            set(1, 3, value!!.code)
        }
        get() = WirelessType.findByCode(get(1, 3))

    var pciTemperature: Int
        set(value) {
            set(4, value)
        }
        get() = get(4)

    init {
        onlineType = get(0)
        wmoduleType = WirelessType.findByCode(get(1, 3))
        pciTemperature = get(4)
    }
}

/**
 * BIT1-3 无线模组型号 0 移远模组BC95HB
1 利尔达模组LSD4NBN
2 M5310模组
3移远模组BC95HA
4-6预留
7 未识别
 */
enum class WirelessType {
    BC95HB(0),
    LSD4NBN(1),
    M5310(2),
    BC95HA(3),
    Reserved1(4),
    Reserved2(5),
    Reserved3(6),
    Unknown(7);

    val code: Int

    constructor(_code: Int) {
        code = _code
    }

    companion object {
        fun findByCode(_code: Int): WirelessType? = WirelessType.values().find { it.code == _code }
    }
}
